#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>

#include <port_comm.h>

using namespace std;
using namespace cv;

int selection = 0;
int *status;
int *quality;
bool *color;

struct ThreadArgument
{
    const char *window;
    int socket;
    pthread_mutex_t *mutex;
};

void *receive(void *arg);
void printUsage();
char *getWindowName(int c);
void processKey(int key);

int main(int argc, char *argv[])
{
    if(argc != 2 && argc != 3)
    {
        printUsage();
        return 1;
    }
    int port_number = atol(argv[1]);
    
    int number;
    bool *isOpen;
    PortComm commServer("localhost", port_number, PortComm::SERVER);
    commServer.read_port((unsigned char **) &isOpen, &number);

    const char* file;
    if(argc == 2)
    {
        file = "/tmp/input/";
    }
    else
    {
        file = argv[2];
    }
    if(file[strlen(file)-1] != '/')
    {
        //Cat a / on to the end
        char* new_file = new char[strlen(file) + 1];
        strcpy (new_file, file);

        strcat (new_file, "/");
        file = new_file; 
    }
    const char *name = "test.jpg";
    char *dest = new char[strlen(file) + strlen(name) + 1];
    strcpy (dest, file);
    strcat (dest, name);
    printf("Saving to %s\n", dest);

    pthread_t *thread = new pthread_t[number];
    ThreadArgument *arg = new ThreadArgument[number];
    status = new int[number];
    quality = new int[number];
    color = new bool[number];
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);
    for(int c = 0; c < number; c++)
    {
        if(!isOpen[c])
        {
            continue;
        }

        // Step 1 Create a socket
        int sock = socket(AF_INET, SOCK_DGRAM, 0);
        if(sock < 0)
        {
            perror("socket creation");
            exit(1);
        }
        arg[c].socket = sock;

        // Step 2 Create a sockaddr_in to describe the local port
        sockaddr_in local_info;
        local_info.sin_family = AF_INET;
        local_info.sin_addr.s_addr = htonl(INADDR_ANY);
        local_info.sin_port = htons(port_number + c + 1);

        // Step 3 Bind the socket to the port
        int r = bind(sock, (sockaddr *) &local_info, sizeof(local_info));
        if(r < 0)
        {
            perror("bind");
            exit(1);
        }

        arg[c].window = getWindowName(c);
        namedWindow(arg[c].window, CV_WINDOW_AUTOSIZE);
        arg[c].mutex = &mutex;

        status[c] = -1;
        quality[c] = 30;
        color[c] = true;

        pthread_create(thread + c, NULL, receive, (void *)(arg + c));
    }
    
    int *message = new int[number];

    while(true)
    {

        int key = waitKey(100);
        processKey(key);
        // cout << selection << "\t" << quality[selection] << "\t" << color[selection] << "\t" << status[selection] << "\n";
        /*for(int c = 0; c < number; c++)
        {
            cout << status[c];
        }*/

        for(int c = 0; c < number; c++)
        {
            if(status[c] == -1)
            {
                message[c] = -1;
            }
            else
            {
                message[c] = quality[c];
                if(color[c])
                {
                    message[c] += 101;
                }
            }
            if(c == 0)
            {
                //cout << message[c] << "\n";
            }
        }

        commServer.write_port((unsigned char *) message, sizeof(int) * number);

    }

    return 0;
}

void *receive(void *arg)
{
    ThreadArgument *targ = (ThreadArgument *) arg;

    int SIZE = 1000000;
    char message[SIZE];

    while(true)
    {
        sockaddr_in incoming_info;
        unsigned int socklen = sizeof(incoming_info);
        int r = recvfrom(targ->socket, &message, SIZE, 0, (sockaddr *) &incoming_info, &socklen);
        for(char c = '0'; c < targ->window[7]; c++)
        {
            cout << "\t";
        }
        if(r < 0)
        {
            perror("recvfrom");
        }

        // Convert to a vector in order to convert
        vector<uchar> buf;
        for(int i = 0; i < r; i++)
        {
            buf.push_back((uchar)message[i]);
        }

        // Incoming image is in jpg, convert to Mat
        Mat matrix = imdecode(buf, 1);

        // TODO: Save image
        cout << "SAVE\n";
        imwrite("/tmp/input/test.jpg", matrix);
        //cout << r << "\n";

        cout << targ->window << "\t" << r << "\t" << matrix.rows << "\t" << matrix.cols << "\n";

        /*if(status[targ->window[7] - '0'] == -1)
        {
            continue;
        }*/
        //pthread_mutex_lock(targ->mutex);
        imshow(targ->window, matrix);
        //pthread_mutex_unlock(targ->mutex);

        //usleep(10000);
        int key = waitKey(1);
        processKey(key);
    }

    return NULL;
}

char *getWindowName(int c)
{
    vector<int> digit;
    if(c == 0)
    {
        digit.push_back(0);
    }
    while(c > 0)
    {
        digit.push_back(c % 10);
        c /= 10;
    }

    char *name = new char[8 + (int)digit.size()];
    name[0] = 'C';
    name[1] = 'a';
    name[2] = 'm';
    name[3] = 'e';
    name[4] = 'r';
    name[5] = 'a';
    name[6] = ' ';
    for(int c = 0; c < (int)digit.size(); c++)
    {
        name[6 + (int)digit.size() - c] = (char)(digit[c] + '0');
    }
    name[7 + (int)digit.size()] = (char) 0;

    cout << name << "\n";
    return name;
}

void printUsage()
{
    printf("usage: server <port> [<save_loc>]\n");
    return;
}

void processKey(int key)
{
    if(key == 'q')
    {
        exit(0);
    }
    else if(key >= '0' && key <= '9')
    {
        selection = key - '0';
    }
    else if(key == 'l')
    {
        quality[selection] += 10;
        if(quality[selection] > 50)
        {
            quality[selection] = 50;
        }
    }
    else if(key == 'k')
    {
        quality[selection] += 1;
        if(quality[selection] > 50)
        {
            quality[selection] = 50;
        }
    }
    else if(key == 'h')
    {
        quality[selection] -= 10;
        if(quality[selection] < 0)
        {
            quality[selection] = 0;
        }
    }
    else if(key == 'j')
    {
        quality[selection] -= 1;
        if(quality[selection] < 0)
        {
            quality[selection] = 0;
        }
    }
    else if(key == 'c')
    {
        color[selection] = !color[selection];
    }
    else if(key == ' ')
    {
        status[selection] = -status[selection];
    }
    else if(key == 'r')
    {
        //LEAKS MEMORY
        status[selection] = -1;
        usleep(1000);
        char *name = getWindowName(selection);
        destroyWindow(name);
        //namedWindow(getWindowName(selection), CV_WINDOW_AUTOSIZE);
    }
}
